/*
 * Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (https://github.com/Ahoo-Wang)].
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *      http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package me.ahoo.cosid.machine;

import com.google.common.base.Objects;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.errorprone.annotations.Immutable;

import java.util.List;

/**
 * Machine state representing the status of a distributed machine instance.
 *
 * <p>This immutable class encapsulates the state information for a machine
 * instance in a distributed ID generation system. It contains:
 *
 * <ul>
 *   <li><strong>Machine ID</strong>: The unique identifier for this machine</li>
 *   <li><strong>Last Timestamp</strong>: The timestamp of the last activity</li>
 * </ul>
 *
 * <p>The machine state is used by {@link MachineIdDistributor} implementations
 * to track and manage machine instances, ensuring that each machine has a
 * unique identifier and that stale instances can be detected and cleaned up.
 *
 * <p>This class provides utility methods for:
 * <ul>
 *   <li>Creating machine states with factory methods</li>
 *   <li>Serializing/deserializing to string format</li>
 *   <li>Equality and hash code computation</li>
 * </ul>
 *
 * @author ahoo wang
 */
@Immutable
public class MachineState {
    /**
     * A sentinel value representing a "not found" machine state.
     *
     * <p>This is used when no valid machine state can be found for a
     * requested machine ID, allowing the framework to handle missing
     * machine states gracefully without null checks.
     */
    public static final MachineState NOT_FOUND = of(-1, -1);

    /**
     * The delimiter used when serializing machine states to string format.
     *
     * <p>This character separates the machine ID and timestamp components
     * in the string representation.
     */
    public static final String STATE_DELIMITER = "|";

    /**
     * The unique identifier for this machine instance.
     *
     * <p>This ID is allocated by the {@link MachineIdDistributor} and
     * ensures global uniqueness of IDs generated by this machine.
     */
    private final int machineId;

    /**
     * The timestamp of the last activity for this machine.
     *
     * <p>This timestamp is used for heartbeat detection and to identify
     * stale machine instances that may have failed or shut down.
     */
    private final long lastTimeStamp;

    /**
     * Create a new MachineState with the specified machine ID and timestamp.
     *
     * @param machineId     The machine ID
     * @param lastTimeStamp The last activity timestamp
     */
    public MachineState(int machineId, long lastTimeStamp) {
        this.machineId = machineId;
        this.lastTimeStamp = lastTimeStamp;
    }

    /**
     * Get the machine ID.
     *
     * @return The machine ID
     */
    public int getMachineId() {
        return machineId;
    }

    /**
     * Get the last activity timestamp.
     *
     * @return The last activity timestamp
     */
    public long getLastTimeStamp() {
        return lastTimeStamp;
    }

    /**
     * Check if this machine state is equal to another object.
     *
     * <p>Two machine states are considered equal if they have the same
     * machine ID, regardless of their timestamps.
     *
     * @param o The object to compare to
     * @return {@code true} if the objects are equal, {@code false} otherwise
     */
    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof MachineState)) {
            return false;
        }
        MachineState that = (MachineState) o;
        return getMachineId() == that.getMachineId();
    }

    /**
     * Get the hash code for this machine state.
     *
     * <p>The hash code is based solely on the machine ID to ensure
     * consistency with the {@link #equals(Object)} implementation.
     *
     * @return The hash code
     */
    @Override
    public int hashCode() {
        return Objects.hashCode(getMachineId());
    }

    /**
     * Get the string representation of this machine state.
     *
     * @return The string representation
     */
    @Override
    public String toString() {
        return "MachineState{"
            + "machineId=" + machineId
            + ", lastTimeStamp=" + lastTimeStamp
            + '}';
    }

    /**
     * Convert this machine state to its string representation.
     *
     * <p>The string format is "machineId|lastTimeStamp", using the
     * {@link #STATE_DELIMITER} to separate the components.
     *
     * @return The string representation
     */
    public String toStateString() {
        return Strings.lenientFormat("%s%s%s", machineId, STATE_DELIMITER, lastTimeStamp);
    }

    /**
     * Create a new MachineState with the specified machine ID and timestamp.
     *
     * @param machineId The machine ID
     * @param lastStamp The last activity timestamp
     * @return A new MachineState instance
     */
    public static MachineState of(int machineId, long lastStamp) {
        return new MachineState(machineId, lastStamp);
    }

    /**
     * Create a new MachineState with the specified machine ID and current timestamp.
     *
     * @param machineId The machine ID
     * @return A new MachineState instance with current timestamp
     */
    public static MachineState of(int machineId) {
        return of(machineId, System.currentTimeMillis());
    }

    /**
     * Create a new MachineState from its string representation.
     *
     * <p>This method parses a string in the format "machineId|lastTimeStamp"
     * and creates a corresponding MachineState instance.
     *
     * @param stateString The string representation
     * @return A new MachineState instance
     * @throws IllegalArgumentException if the string format is invalid
     */
    public static MachineState of(String stateString) {
        List<String> stateSplits = Splitter.on(STATE_DELIMITER).omitEmptyStrings().splitToList(stateString);
        if (stateSplits.size() != 2) {
            throw new IllegalArgumentException(Strings.lenientFormat("Machine status data:[{%s}] format error.", stateString));
        }
        int machineId = Integer.parseInt(stateSplits.get(0));
        long lastStamp = Long.parseLong(stateSplits.get(1));
        return MachineState.of(machineId, lastStamp);
    }
}
